6장. 공생성(Connascence)
출처: 『소프트웨어 설계의 결합 균형』(블라드 코노노프 지음, 장연호 옮김, 제이펍 2026) | 공식: https://www.jpub.kr/
"두 모듈이 함께 태어났다" — 한 쪽을 바꾸면 다른 쪽도 따라 바뀌어야 하는 모든 관계를 공생성(connascence)이라 부른다. 5장의 6수준 모듈 결합보다 세밀하고, 정적(컴파일 타임)과 동적(런타임) 두 축으로 나눠 9가지 종류를 구분한다. — 메일리어 페이지존스(Meilir Page-Jones, 1996)
학습 목표
이 장을 끝내면 다음을 할 수 있다.
- 공생성의 어원·정의와 모듈 결합과의 차이를 설명한다.
- 정적 공생성 5가지(이름·유형·의미·알고리즘·위치)를 코드와 비유로 구분한다.
- 동적 공생성 4가지(실행·타이밍·값·동일)를 코드와 비유로 구분한다.
- 두 모듈 간 공생성이 여러 개 겹칠 때 전체 수준을 판단한다(가장 높은 수준이 대표).
- 높은 수준 공생성을 낮은 수준으로 리팩토링하거나, 낮출 수 없을 때 이유를 설명한다.
- 구조적 설계의 모듈 결합과 공생성의 사각지대를 설명하고 통합 강도(7장 선수)가 왜 필요한지 말한다.
전체 흐름도
[ 공생성 = "함께 태어남" — 한쪽 변경 시 다른쪽도 변경 필요 ]
│
├─ 정적 공생성 (컴파일 타임 / 코드 읽어서 식별)
│ │ 공유 지식: 이름 → 유형 → 의미 → 알고리즘 → 위치 (점점 넓어짐)
│ ├─ 1. 이름 공생성 (CoN) ─ 가장 약함 / 가장 명시적
│ ├─ 2. 유형 공생성 (CoT)
│ ├─ 3. 의미 공생성 (CoM)
│ ├─ 4. 알고리즘 공생성 (CoA)
│ └─ 5. 위치 공생성 (CoP) ─ 정적 중 가장 강함 / 가장 암묵적
│
└─ 동적 공생성 (런타임 / 실행 중 식별)
│ → 정적 공생성 전체보다 더 강한 종속성
├─ 6. 실행 공생성 (CoE) ─ 동적 중 가장 약함
├─ 7. 타이밍 공생성 (CoTi)
├─ 8. 값 공생성 (CoV)
└─ 9. 동일 공생성 (CoI) ─ 가장 강함
[ 여러 수준이 겹치면 → 가장 높은 수준이 전체 수준을 대표 ]
[ 높은 공생성 = "함께 살아야" → 억지로 낮추기보다 가까이 두는 설계 고려 ]
[ 두 모델의 사각지대 → 7장 통합 강도(Integration Strength)로 통합 ]
0. 사전 필수 용어
| 용어 | 정의 |
|---|---|
| 공생성 (Connascence) | 라틴어 con(함께) + nascere(태어남). 두 모듈의 수명주기가 얽혀, 한 모듈을 바꿀 때 다른 모듈도 바꿔야 하는 관계 |
| 정적 공생성 (Static Connascence) | 소스 코드·컴파일 타임 수준의 상호 연결. 코드 리뷰·정적 분석으로 식별 가능 |
| 동적 공생성 (Dynamic Connascence) | 런타임 동작 수준의 상호 연결. 실행 중에야 드러나며 정적보다 강하다 |
| 마법 값 (Magic Value) | 코드에 하드코딩된 숫자·문자열로, 그 의미를 코드만 보면 알 수 없는 값. 예: newStatus=7 |
| 위치 민감 인터페이스 | 인수 순서가 의미를 결정하는 인터페이스. 순서를 실수하면 조용히 틀린 결과가 나온다 |
| 트랜잭션 일관성 (Transactional Consistency) | 여러 데이터 변경이 원자적으로 성공하거나 실패해야 하는 보장. 값 공생성·동일 공생성의 핵심 배경 |
| 열거형 (Enum) | 의미 공생성을 줄이기 위한 리팩토링 도구. 마법 값 → 이름 있는 상수로 격상 |
| 모듈 결합 vs 공생성 | 모듈 결합(5장)은 통신 방식을 6단계로 분류, 공생성은 공유 지식의 종류를 9단계로 분류. 동일 현상의 다른 측면 |
| 공생성 전체 수준 | 두 모듈 사이에 여러 공생성이 동시에 존재할 때, 가장 높은 수준이 전체 공생성을 대표 |
| 통합 강도 (Integration Strength) | 모듈 결합과 공생성의 사각지대를 보완하는 7장의 통합 모델. 4수준(계약·모델·기능·침입 결합)으로 구성 |
1. 공생성이란? — "함께 태어난" 모듈
일상 비유: 쌍둥이를 떠올려 보자. 한 아이가 독감에 걸리면 부모는 다른 아이도 검사해야 한다. 별개 아이들이면 "나만 아프면 그만"이지만, 함께 태어나 같은 공간에서 지내는 쌍둥이는 한 명의 변화가 자동으로 다른 명의 검토를 요구한다.
소프트웨어에서 공생성이 정확히 이 관계다. 한 모듈을 변경할 때 다른 모듈도 변경해야 하거나, 최소한 세심하게 검토해야 한다면, 둘은 공생성 관계다.
또한 두 모듈을 동시에 변경해야 할 합리적인 요구사항 변경이 예상되는 경우에도 공생성 관계로 볼 수 있다.
5장의 모듈 결합이 "어떤 방식으로 연결됐나(전역 변수인가, 플래그인가, 데이터만인가)"를 물었다면, 공생성은 "어떤 종류의 지식을 공유해야 하는가"를 더 세밀하게 물어본다.
핵심 통찰 — 공생성 수준이 높을수록 두 모듈은 더 많은 지식을 함께 알아야 하고, 한 쪽 변경이 다른 쪽에 미치는 충격이 커진다. 반대로 공생성이 낮으면 한 쪽이 바뀌어도 다른 쪽은 그대로일 수 있다.
2. 정적 공생성 — 코드만 읽어도 보이는 결합
정적 공생성은 소스 코드 수준의 상호 의존. 이론적으로 정적 코드 분석 도구로 자동화할 수 있다. 공유 지식은 이름에서 위치로 갈수록 넓어지고, 인터페이스는 명시적에서 암묵적으로 바뀐다(그림 6.1 원서).
2.1 이름 공생성 (CoN) — 같은 이름표를 달아야 한다
일상 비유: 아파트 우편함에 이름표가 붙어 있다. 택배기사는 이름표에 적힌 이름을 보고 문을 두드린다. 내가 이름을 바꾸면 택배기사에게 새 이름표를 알려야 한다 — 그렇지 않으면 택배가 영영 도착 안 한다.
소프트웨어: 연결된 모듈이 같은 이름으로 같은 것을 가리켜야 하는 관계. 변수명·메서드명·서비스 엔드포인트 이름이 모두 여기 해당한다.
잘못된 예 / 올바른 예
# 잘못된 예: name을 nm으로 바꾸면 두 행이 동시에 깨짐
def greet(name): # 01행 — name 정의
message = f'Hello, {name}!' # 02행 — name 사용 (이름 공생성)
print(message) # 03행 — message 사용 (이름 공생성)
greet('world') # 05행 — greet 이름 공유 (이름 공생성)
# 올바른 예: 이름 변경은 IDE 리네임으로 한 번에 — 이름 공생성은 불가피하지만 관리 가능
def greet(name: str) -> None:
message = f'Hello, {name}!'
print(message)
greet('world')
이름 공생성은 가장 약한 수준이다. 현대 IDE의 "리네임 리팩토링" 한 번으로 대부분 해결된다. 불가피한 결합이기도 하다 — 이름이 없으면 서로를 가리킬 수 없다. 같은 근거가 클래스 공개 메서드, 웹 서비스 HTTP 동사·엔드포인트에도 동일하게 적용된다.
2.2 유형 공생성 (CoT) — 같은 형식의 서류를 써야 한다
일상 비유: 해외 취업 서류는 반드시 영어로 제출해야 한다. 이름을 맞게 써도 언어(형식)가 틀리면 무효다. 한국어로 완벽하게 쓴 자소서는 받아주지 않는다.
소프트웨어: 이름뿐 아니라 타입도 동일해야 하는 관계. 강타입 언어(C#, Java, TypeScript)에서 두드러지고, 동적 타입 언어(Python, JS)에서도 암묵적 타입 가정은 남아 있다. 다른 유형의 값이 제공되면 코드는 유효하지만 실행 중에 실패할 수 있다.
잘못된 예 / 올바른 예
// 잘못된 예: name 인수에 int를 넘기면 컴파일 오류 (유형 공생성 위반)
private static void Greet(string name) {
string message = $"Hello, {name}";
Console.WriteLine(message);
}
static void Main() {
Greet("world"); // string — 올바름
Greet(42); // int — 컴파일 오류 (유형 공생성 위반)
}
// 올바른 예: 계약을 명시적으로 선언해 컴파일러가 검사하게
private static void Greet(string name) {
Console.WriteLine($"Hello, {name}");
}
유형 공생성은 이름 공생성보다 한 단계 강하다. 둘은 거의 항상 함께 나타나며, 유형에 독립적인 통합은 매우 드물다.
2.3 의미 공생성 (CoM) — 비밀 암호를 둘이 나눠 갖는다
일상 비유: 도서관 내부 코드 시스템에서 장르코드 = 7이 "SF"를 뜻한다고 사서와 서가 담당자가 약속했다. 새 직원이 7의 의미를 모르면 책을 엉뚱한 곳에 꽂는다. 코드북을 공유하지 않으면 아무도 알 수 없는 마법 값이다.
소프트웨어: 두 모듈이 특정 값에 특별한 의미를 부여하기로 암묵적으로 합의한 관계. 이 의미는 코드만 봐서는 알 수 없다. 컴파일러가 검증할 수 없고, 실수하기 쉽다.
잘못된 예 / 올바른 예
# 잘못된 예: 7이 무슨 뜻인지 코드만으로 알 수 없음 (의미 공생성)
def process_email(msg, case_id):
support_case = repo.load(case_id)
support_case.append_response(msg.body, new_status=7) # 7 = Reopened???
# 올바른 예: 열거형으로 의미를 명시 → 의미 공생성 → 유형+이름 공생성으로 격상
from enum import Enum
class Status(Enum):
Open = 0
FollowUp = 1
OnHold = 2
Escalated = 3
Closed = 4
Resolved = 5
Reopened = 6
def process_email(msg, case_id):
support_case = repo.load(case_id)
support_case.append_response(msg.body, new_status=Status.Reopened) # 명확!
열거형 하나로 마법 값을 이름 있는 상수로 바꾼다. 컴파일러가 유효하지 않은 값을 잡아주고, 인터페이스가 자기 설명적이 된다. 기술적으로는 이름 공생성+유형 공생성 둘 다이지만, 전통적으로 가장 높은 수준만 표기한다.
2.4 알고리즘 공생성 (CoA) — 암호 해독법을 둘이 같이 알아야 한다
일상 비유: 스파이 영화의 암호 통신을 떠올려 보자. 발신자와 수신자가 같은 암호 방식을 써야 메시지가 해독된다. 발신자는 카이사르 암호를 쓰고 수신자가 비즈네르 암호로 해독하려 하면 — 아무 의미 없는 글자들이 나온다.
소프트웨어: 두 모듈이 교환하는 값의 의미를 이해하기 위해 같은 알고리즘을 사용하기로 합의한 관계. 데이터 암호화·해시·직렬화 방식이 전형적인 예다.
중요한 오해 방지: 알고리즘 공생성은 코드 중복에 관한 것이 아니다. 알고리즘이 외부 라이브러리에 있든 복제돼 있든, 둘이 같은 알고리즘에 동의해야 한다는 사실이 핵심이다. 이 때문에 공생성 척도에서 비교적 낮은 순위(9점 만점에 4점)를 받는다.
잘못된 예 / 올바른 예
# 잘못된 예: 업로드 쪽은 MD5, 다운로드 쪽은 SHA-256 → 항상 불일치
def upload_file(file_path: str):
data = read_file(file_path)
checksum = calculate_md5(data) # MD5 사용
storage.upload(data, checksum)
def verify_file(data: bytes, checksum: str) -> bool:
return calculate_sha256(data) == checksum # SHA-256 → 항상 실패
# 올바른 예: 알고리즘을 공유 상수로 단일화
HASH_ALGORITHM = "md5" # 단일 출처 (Single Source of Truth)
def upload_file(file_path: str):
data = read_file(file_path)
checksum = calculate_checksum(data, HASH_ALGORITHM)
storage.upload(data, checksum)
def verify_file(data: bytes, checksum: str) -> bool:
return calculate_checksum(data, HASH_ALGORITHM) == checksum
2.5 위치 공생성 (CoP) — 줄 서는 순서가 의미를 결정한다
일상 비유: 공연 티켓 구매 양식에 "이름, 좌석번호, 공연일" 순으로 적어야 한다. 순서를 바꿔 "공연일, 이름, 좌석번호"로 적으면 시스템이 공연일을 이름으로, 이름을 좌석번호로 해석한다. 내용이 맞아도 위치가 틀리면 오류다.
소프트웨어: 여러 모듈이 요소의 순서에 합의해야 하는 관계. 배열 인덱스, 동일 타입 인수 목록, 이름 없는 튜플이 전형적인 예다.
잘못된 예 / 올바른 예
# 잘못된 예 A: 배열 인덱스로 의미를 결정 (코드 6.6 패턴 — 원서)
def send_email(data: list):
from_addr = data[0] # 순서 암묵 합의
to_addr = data[1]
subject = data[2]
body = data[3]
# 잘못된 예 B: 같은 타입 여러 인수 (코드 6.7 패턴) — from/to 순서를 실수하기 쉬움
def send_email(from_addr: str, to_addr: str, subject: str, body: str):
...
# 잘못된 예 C: 이름 없는 튜플 반환 (코드 6.8 패턴) — 어떤 값이 먼저인지 모름
def get_current_datetime():
local_time = datetime.now()
utc_time = datetime.utcnow()
return (local_time, utc_time) # 순서 공유 필요
# 올바른 예: named 인수나 데이터 클래스로 위치 의존 제거
from dataclasses import dataclass
@dataclass
class EmailMessage:
from_addr: str
to_addr: str
subject: str
body: str
def send_email(msg: EmailMessage):
# 이제 순서는 중요하지 않고 이름이 의미를 결정
...
위치 공생성은 정적 공생성 중 가장 강한 수준이다. 겉보기에 이름 공생성과 비슷해 보이지만, 하나는 인터페이스를 명시적으로 만들고 다른 하나는 암묵적으로 남겨 오류를 유발한다. 통합 가이드라인을 참조하지 않고서는 안전하게 통합할 수 없다.
3. 동적 공생성 — 실행 중에야 보이는 결합
동적 공생성은 런타임 수준의 의존. 코드를 아무리 들여다봐도 실행해 보지 않으면 보이지 않는 결합이 여기 해당한다. 동적 공생성은 정적 공생성 전체보다 더 강한 종속성을 만들며, 모듈 간에 공유되는 지식의 범위가 더 넓어진다(그림 6.2 원서).
3.1 실행 공생성 (CoE) — 악기 합주의 입장 순서
일상 비유: 오케스트라 합주를 생각해 보자. 타악기가 먼저 들어오고, 그 다음에 현악기, 마지막에 관악기가 들어온다. 관악기가 현악기보다 먼저 나오면 연주가 망가진다. 순서 자체가 음악의 일부다.
소프트웨어: 모듈들이 특정 순서로 실행되어야 하는 관계. 정적 공생성에서의 위치 공생성에 대응하는 동적 공생성이다.
코드 예시
// 실행 공생성의 교과서 예 — DB 트랜잭션 순서를 어기면 런타임 오류 (코드 6.9 원서)
interface DbConnection {
void OpenConnection(); // 1번 먼저 — 다른 모든 메서드의 전제
void BeginTransaction(UUID id); // 2번 — OpenConnection 후에만
QueryResult ExecuteQuery(String sql); // 3번 — BeginTransaction 후에만
void Commit(UUID id); // 4번 — ExecuteQuery 후에만
void Rollback(UUID id); // 4번 대안
void CloseConnection(); // 5번 마지막
}
// 잘못된 사용: 런타임 오류
conn.Commit(txId); // 오류! OpenConnection 전에 커밋 시도
conn.OpenConnection();
// 올바른 사용: 정해진 순서
conn.OpenConnection();
conn.BeginTransaction(txId);
conn.ExecuteQuery("SELECT ...");
conn.Commit(txId);
conn.CloseConnection();
실행 공생성은 런타임 종속이기 때문에 컴파일러가 잡아주지 않는다. 메서드 간의 런타임 종속성은 기능이 서로 밀접하게 관련되어 있음을 나타낸다. Template Method 패턴, 상태 기계(State Machine)로 순서를 강제할 수 있다.
3.2 타이밍 공생성 (CoTi) — 정확한 배달 시간이 있는 택배
일상 비유: "오전 10시~12시 사이 배달"인 택배를 받으려면 그 시간에 집에 있어야 한다. 10시 5분에 나가면 택배가 왔다가 돌아간다. 순서뿐 아니라 시간 간격이 중요하다.
소프트웨어: 순서뿐 아니라 두 실행 사이의 시간 간격에도 의존하는 관계. DB 연결 타임아웃, 시스템 클록 이중 조회가 전형적인 예다. 실시간 시스템에는 타이밍 관계로 인한 공생성이 다양하게 존재한다(자동차 문 자동 잠금, X선 기계 비활성화 등).
잘못된 예 / 올바른 예
// 잘못된 예: 시스템 클록을 두 번 조회 (코드 6.10 원서)
// — 두 호출 사이에 분이 바뀌거나 일광절약 시간제 전환이 발생하면 잘못된 결과
(int, int) GetTime() {
int hour = DateTime.Now.Hour; // 첫 번째 클록 조회 (02행)
int minute = DateTime.Now.Minute; // 두 번째 클록 조회 (03행) — 시간차 가능
return (hour, minute);
}
// 23:59에 hour=23, 그 사이 분 전환 시 minute=0 → 23:00이라는 오류
// 올바른 예: 클록을 한 번만 조회 (코드 6.11 원서) — 타이밍 의존 제거
(int, int) GetTime() {
DateTime now = DateTime.Now; // 한 번만 — now.Hour, now.Minute은 같은 순간
int hour = now.Hour;
int minute = now.Minute;
return (hour, minute);
}
타이밍 공생성은 가장 발견하기 어려운 공생성 중 하나다. 타이밍 불일치는 특정 환경·부하 조건에서만 나타나고 재현이 어렵다. 이벤트/트랜잭션 타임스탬프 불일치는 잘못된 데이터 분석, 이벤트 순서 역전으로 이어질 수 있다.
주의: DB 연결 30초 타임아웃처럼 비즈니스 요구사항 자체가 타이밍 간격을 규정하는 경우, 리팩토링으로 완전히 제거할 수 없다. 구현 부주의로 생긴 타이밍 의존만 제거 대상이다.
3.3 값 공생성 (CoV) — 삼각형의 세 변은 혼자 바꿀 수 없다
일상 비유: 정삼각형에서 한 변의 길이를 늘리면 삼각형 모양을 유지하려면 다른 두 변도 바뀌어야 한다. 또는 소매점 재고 — 판매 수량을 올리면 재고 수량을 그만큼 줄여야 한다. 둘 다 안 바꾸면 장부가 틀린다. 한 값을 바꾸려면 연관된 다른 값도 함께 바꿔야 유효한 상태를 유지한다.
소프트웨어: 시스템의 여러 값이 동시에 변경되어야 하는 강한 기능적 관계. 산술 제약이나 비즈니스 규칙이 이 관계를 만든다. 한 값만 바꾸면 시스템이 불법 상태(illegal state)가 된다.
코드 예시
// 산술 제약에 의한 값 공생성 — 삼각형 세 변 (코드 6.12 원서)
class Triangle {
double EdgeA { get; private set; }
double EdgeB { get; private set; }
double EdgeC { get; private set; }
// 제약: A < B+C, B < A+C, C < A+B (삼각형 부등식)
void SetEdges(double a, double b, double c) {
if (a >= b + c || b >= a + c || c >= a + b)
throw new InvalidOperationException("삼각형 조건 위반");
EdgeA = a; EdgeB = b; EdgeC = c; // 세 개를 원자적으로 변경
}
}
// 비즈니스 규칙에 의한 값 공생성 (코드 6.13 원서)
// — 고객 검증 취소 시 우선 배송 자격도 함께 취소
class Customer {
bool isVerified { get; private set; }
bool priorityShippingEnabled { get; private set; }
void ClearVerification() {
isVerified = false;
priorityShippingEnabled = false; // 함께 바뀌어야 일관성 유지
}
void AllowPriorityShipping() {
if (isVerified) {
priorityShippingEnabled = true;
}
}
}
값 공생성이 있는 필드들은 단일 메서드로 묶어 원자적 변경을 강제하는 것이 전형적인 해결책이다.
3.4 동일 공생성 (CoI) — 같은 냄비에서 같이 끓어야 한다
일상 비유: 공유 주방이 있는 쉐어하우스에서 냄비가 딱 한 개뿐이다. 두 사람이 각자 별개 냄비를 사용하면 음식이 따로 익는다. 하지만 공동 냄비를 함께 써야 한다면, 한 사람이 냄비를 바꾸거나 치우면 다른 사람 요리가 즉시 영향받는다. 정확히 동일한 인스턴스를 공유해야 올바르게 동작하는 관계다.
소프트웨어: 두 모듈이 올바르게 동작하려면 세 번째 객체의 정확히 같은 인스턴스를 함께 참조해야 하는 가장 강한 공생성. 공유 객체의 모든 변경사항은 연결된 모듈에서 즉시 관찰 가능하다.
코드 예시
# 잘못된 예: 두 모듈이 각자 다른 DB 커넥션 풀 인스턴스 생성
# — 리소스 낭비 + 트랜잭션 일관성 보장 불가
class ModuleA:
def __init__(self):
self.pool = ConnectionPool() # 자기만의 풀
class ModuleB:
def __init__(self):
self.pool = ConnectionPool() # 또 다른 풀 — 커넥션 수 두 배 소비
# 올바른 예: 단일 인스턴스를 의존성 주입으로 공유 — 동일 공생성을 명시적으로 관리
shared_pool = ConnectionPool(max_connections=10)
class ModuleA:
def __init__(self, pool: ConnectionPool):
self.pool = pool # 외부에서 주입받음
class ModuleB:
def __init__(self, pool: ConnectionPool):
self.pool = pool # 같은 인스턴스 주입
module_a = ModuleA(shared_pool)
module_b = ModuleB(shared_pool) # 정확히 같은 shared_pool 참조
동일 공생성은 가장 강한 수준이다. 분산 시스템에서는 그림 6.4(원서)처럼 두 서비스가 같은 DB에 쓰고 읽으며 트랜잭션 일관성을 가정할 때 동일 공생성이 발생한다. 단, 메시지 버스를 통해 통신하고 일관성을 가정하지 않으면 동일 공생성이 아닐 수 있다.
4. 공생성 평가하기 — 실전 예시 (코드 6.14 원서)
실전 코드에는 여러 공생성이 동시에 나타난다. 다음 호출을 분석해 보자.
# retail 모듈 → accounting 모듈로의 호출
(res, balance, tran_id) = accounting.process_payment(
account_id='LVG141028',
transaction_type=3, # 마법 값 3 = 어떤 의미?
credit_card='S5hDn175mPiDL4D5ftbtMw==' # AES 암호화된 값
)
이 한 줄에서 발생하는 공생성:
| 공생성 수준 | 발생 이유 |
|---|---|
| 이름 공생성 | process_payment, account_id, transaction_type, credit_card 이름 공유 |
| 유형 공생성 | account_id·credit_card는 문자열, transaction_type은 숫자여야 함 |
| 의미 공생성 | transaction_type=3의 의미를 두 모듈이 암묵 공유 |
| 알고리즘 공생성 | credit_card 값을 AES 알고리즘으로 암호화·복호화에 합의 |
| 위치 공생성 | 반환값 튜플 (res, balance, tran_id)의 순서를 공유 |
전체 수준: 위치 공생성(가장 높은 수준). 여러 공생성이 겹칠 때는 가장 높은 수준이 전체를 대표한다. 단, 동적 공생성 가능성도 있으므로 실제로는 더 높을 수 있다.
5. 공생성 관리하기
공생성은 항상 낮출 수 있는 것이 아니다. 리팩토링으로 낮출 수 있는 경우와 없는 경우를 구분하는 것이 실무의 핵심이다.
| 공생성 | 리팩토링 방법 | 낮출 수 있나? |
|---|---|---|
| 의미 → 유형/이름 | 열거형 추출 | 대부분 가능 |
| 위치 → 이름 | Named 인수 / 데이터 클래스 | 가능 |
| 타이밍 | 클록 단일 조회 | 구현 부주의는 가능, 비즈니스 요구는 불가 |
| 알고리즘 | 알고리즘 통일 | 경우에 따라 |
| 값 | 원자적 변경 강제 | 비즈니스 요구는 제거 불가 |
| 동일 | 의존성 주입으로 명시화 | 요구 자체는 제거 불가 |
핵심 통찰 — 높은 공생성을 낮추려는 목표가 항상 옳은 것은 아니다. 높은 공생성은 그 모듈들이 함께 살아야 한다는 신호이기도 하다. 분리하는 것보다 가까이 두는 것이 더 나은 선택일 수 있다(8장 거리·10장 결합 균형에서 자세히 다룬다).
6. 모듈 결합 vs 공생성: 두 모델의 한계와 사각지대
6.1 콘텐츠 결합의 역설
// 코드 6.15 원서 — 리플렉션으로 비공개 속성 접근
var customer = LoadNextCustomer();
var value = typeof(Customer)
.GetProperty("_verificationStatus")
.GetValue(customer);
- 모듈 결합 관점: 비공개 인터페이스 사용 → 가장 강한 콘텐츠 결합
- 공생성 관점: 비공개 필드 이름(
_verificationStatus)만 알면 됨 → 가장 약한 이름 공생성
이 역설은 두 모델이 다른 측면을 측정하기 때문이다. 모듈 결합은 인터페이스 유형을, 공생성은 공유 지식의 복잡도를 측정한다.
6.2 두 모델의 공통 사각지대 — 물리적 통합 없는 지식 공유
두 서비스가 완전히 동일한 비즈니스 로직(예: 무료 배송 자격 규칙)을 각자 구현한 경우:
# Retail 서비스와 Fulfillment 서비스가 동일한 비즈니스 로직을 중복 구현
def is_qualified_for_free_shipping(order):
return active_promotions.apply_for(order) or order.total_cost > 1000
# → 물리적 연결 없음 → 모듈 결합도 아님, 알고리즘 공생성도 아님
# → 그러나 규칙 변경 시 두 서비스를 동시에 변경해야 함 → 강한 종속성 존재
이 상황은 모듈 결합도, 공생성도 포착하지 못한다. 두 모델의 공통 사각지대다. → 7장 통합 강도 (Integration Strength)가 이를 해결한다.
핵심 개념 정리
| 공생성 | 유형 | 핵심 신호 | 비유 |
|---|---|---|---|
| 이름 (CoN) | 정적 | 이름 하나 바꾸면 두 곳이 깨짐 | 우편함 이름표 |
| 유형 (CoT) | 정적 | 타입 불일치 시 컴파일 오류 | 해외 서류 언어 형식 |
| 의미 (CoM) | 정적 | 마법 값·하드코딩 상수 | 도서관 내부 코드번호 |
| 알고리즘 (CoA) | 정적 | 암호화·해시 알고리즘 합의 | 스파이 암호 해독법 |
| 위치 (CoP) | 정적 | 배열 인덱스·인수 순서 의존 | 티켓 양식 항목 순서 |
| 실행 (CoE) | 동적 | 메서드 호출 순서 강제 | 오케스트라 입장 순서 |
| 타이밍 (CoTi) | 동적 | 두 동작 사이 시간 간격 의존 | 배달 시간 맞추기 |
| 값 (CoV) | 동적 | 여러 값이 동시에 변경되어야 일관성 유지 | 삼각형 세 변 |
| 동일 (CoI) | 동적 | 정확히 같은 인스턴스를 참조해야 작동 | 쉐어하우스 공동 냄비 |
실무 체크리스트
- [ ] 코드에 숫자·문자열 마법 값이 모듈 경계를 넘어 공유되는가? → 열거형으로 의미 공생성을 낮춰라.
- [ ] 배열 인덱스나 동일 타입 인수 목록으로 의미를 전달하는 함수가 있는가? → Named 인수나 데이터 클래스로 위치 공생성을 낮춰라.
- [ ] 두 모듈이 같은 데이터를 암호화·해시할 때 알고리즘을 공유 상수나 라이브러리로 단일화했는가?
- [ ] API·인터페이스를 설계할 때 반환값이 이름 없는 튜플인가? → 이름 있는 객체로 위치 공생성을 제거하라.
- [ ] 시스템 클록을 짧은 시간에 여러 번 조회하는 코드가 있는가? → 한 번 조회해 변수에 담고 재사용하라.
- [ ] 높은 공생성(값·동일)을 억지로 낮추려 하고 있는가? → 해당 모듈들을 가까이 두는 방향을 먼저 고려하라.
- [ ] 여러 공생성이 겹치는 인터페이스에서 전체 공생성 수준을 "가장 높은 것"으로 판단했는가?
- [ ] 두 모듈이 물리적으로 연결되지 않아도 동일한 비즈니스 로직을 중복 구현하고 있지는 않은가? → 모듈 결합·공생성 모델의 사각지대이므로 통합 강도(7장) 관점으로 점검하라.
연습문제 (문제만 — 정답은 부록 D)
-
분류. 다음 코드의 공생성 수준은 무엇인가? 근거를 한 문장으로 답하라.
python def calculate_area(width, height): return width * height area = calculate_area(10, 5) # 인수 순서를 실수로 바꾸면?힌트: 이름과 위치를 모두 따져보라. -
리팩토링. 다음 코드의 의미 공생성을 유형/이름 공생성으로 낮춰라.
python def set_order_status(order_id, status_code): # status_code: 1=pending, 2=shipped, 3=delivered, 4=cancelled db.update("orders", {"status": status_code}, {"id": order_id}) -
분석. 두 마이크로서비스 A·B가 Redis의 같은 키에 각자 데이터를 쓰고, 서로의 변경 사항을 즉시 읽는다. 어느 수준의 공생성인가? 그 이유는?
-
판단. 타이밍 공생성이 있는 시스템에서 "리팩토링으로 타이밍 공생성을 완전히 제거하자"는 주장이 옳은가? DB 연결 30초 타임아웃을 예로 들어 답하라.
-
종합. 코드 6.14의
process_payment호출에서 공생성 수준을 하나씩 열거하고, 각각을 어떻게 낮출 수 있는지 제안하라. 낮출 수 없는 것이 있다면 이유를 설명하라.
최신 동향 (검증 2026-05-21)
공생성 모델 자체(Page-Jones 1996)는 안정된 이론이며 내용 변경 없이 유효하다. 다만 현대 도구·아키텍처와의 접점이 성숙하고 있다.
- connascence.io에서 9가지 공생성을 인터랙티브하게 탐색할 수 있다. 각 수준별 강도(degree)·지역성(locality) 가이드라인도 제공한다.
- OpenAPI / AsyncAPI 같은 계약 우선(contract-first) 도구가 확산되면서, 의미·알고리즘·위치 공생성을 명시적 계약으로 고정하는 방법이 표준화되고 있다. 이름·유형 공생성은 계약으로 표현하기 쉽지만, 값·동일 공생성은 여전히 운영 설계로 관리해야 한다.
- gRPC·Protobuf 같은 이진 직렬화 프로토콜은 알고리즘·위치 공생성을 스키마 수준에서 고정하여 런타임 불일치를 컴파일 타임 오류로 앞당기는 실천이 확산되고 있다.
부록 A. 용어 사전
| 한글 용어 | 원문 영문명 | 의미 |
|---|---|---|
| 공생성 | Connascence | "함께 태어남" — 한 모듈 변경 시 다른 모듈도 변경해야 하는 관계 |
| 정적 공생성 | Static Connascence | 소스 코드·컴파일 타임 수준의 공생성 |
| 동적 공생성 | Dynamic Connascence | 런타임 수준의 공생성. 정적보다 강함 |
| 이름 공생성 | Connascence of Name (CoN) | 같은 이름을 공유해야 하는 가장 약한 공생성 |
| 유형 공생성 | Connascence of Type (CoT) | 같은 타입을 공유해야 하는 공생성 |
| 의미 공생성 | Connascence of Meaning (CoM) | 마법 값의 의미를 공유해야 하는 공생성 |
| 알고리즘 공생성 | Connascence of Algorithm (CoA) | 같은 알고리즘(암호화 등)을 사용해야 하는 공생성 |
| 위치 공생성 | Connascence of Position (CoP) | 요소의 순서를 공유해야 하는 공생성. 정적 최강 |
| 실행 공생성 | Connascence of Execution (CoE) | 특정 순서로 실행되어야 하는 동적 공생성 |
| 타이밍 공생성 | Connascence of Timing (CoTi) | 시간 간격에 의존하는 동적 공생성 |
| 값 공생성 | Connascence of Value (CoV) | 여러 값이 동시에 변경되어야 하는 동적 공생성 |
| 동일 공생성 | Connascence of Identity (CoI) | 정확히 같은 인스턴스를 참조해야 하는 가장 강한 공생성 |
| 마법 값 | Magic Value | 의미를 알아야만 해독할 수 있는 하드코딩된 값 |
| 공생성 전체 수준 | Overall Connascence Level | 여러 공생성이 겹칠 때 가장 높은 수준이 대표 |
| 통합 강도 | Integration Strength | 7장에서 모듈 결합+공생성의 사각지대를 보완하는 통합 모델 |
부록 B. 핵심 비교표
9가지 공생성 한눈에
| 번호 | 이름 | 유형 | 강도 | 식별 방법 | 주요 리팩토링 |
|---|---|---|---|---|---|
| 1 | 이름 | 정적 | 가장 약함 | 이름 불일치 → 컴파일/런타임 오류 | IDE 리네임 |
| 2 | 유형 | 정적 | 약함 | 타입 불일치 → 컴파일 오류 | 타입 명시화 |
| 3 | 의미 | 정적 | 중하 | 마법 값·하드코딩 상수 | 열거형 추출 |
| 4 | 알고리즘 | 정적 | 중 | 알고리즘 합의 필요 | 공유 라이브러리 단일화 |
| 5 | 위치 | 정적 | 중상 | 인수·반환 순서 의존 | Named 인수·데이터 클래스 |
| 6 | 실행 | 동적 | 상 | 순서 위반 시 런타임 오류 | Template Method·상태 기계 |
| 7 | 타이밍 | 동적 | 상 | 시간차로 인한 간헐 오류 | 클록 단일 조회·동기화 |
| 8 | 값 | 동적 | 매우 강함 | 불일치 시 불법 상태 | 원자적 변경 묶기 |
| 9 | 동일 | 동적 | 가장 강함 | 인스턴스 분리 시 오동작 | 의존성 주입·싱글턴 |
정적 vs 동적 공생성
| 구분 | 정적 공생성 | 동적 공생성 |
|---|---|---|
| 식별 시점 | 코드 리뷰·정적 분석 | 런타임 실행 |
| 강도 | 상대적으로 약함 | 정적 전체보다 강함 |
| 자동화 | 정적 분석 도구 가능 | 런타임 모니터링 필요 |
| 예 | 이름·유형·의미·알고리즘·위치 | 실행·타이밍·값·동일 |
5장 모듈 결합 vs 6장 공생성 관계
| 모듈 결합(5장) | 관련 공생성(6장) | 설명 |
|---|---|---|
| 데이터 결합 | 모든 정적 공생성 가능 | 통합에 필요한 최소 데이터만 교환 |
| 스탬프 결합 | 유형 이상 정적 공생성 | 필요 이상의 데이터 구조 공유 |
| 제어 결합 | 공생성 모델에 직접 대응 없음 | 내부 분기를 외부가 제어하는 구조 |
| 외부 결합 | 동일 공생성(최종) | 전역 변수 → 강한 일관성 가정 |
| 공통 결합 | 동일 공생성(최종) | 외부 결합의 확장판 |
| 콘텐츠 결합 | 이름 공생성(의외!) | 비공개 필드명만 알면 되는 역설 |
흥미로운 역설 — 5장에서 가장 강한 콘텐츠 결합이, 공생성 관점에서는 가장 약한 이름 공생성에 해당할 수 있다. 두 모델은 같은 현상의 다른 측면을 측정하기 때문이다. 이 역설이 7장 통합 강도 모델의 출발점이다.
부록 C. 추천 참고 자료 & 링크
Tier 1 공식·표준 (생존 확인 2026-05-21)
| 자료 | 링크 |
|---|---|
| 책 공식 (제이펍) | jpub.kr |
| 원서 — Manning | Balancing Coupling in Software Design |
| 공생성 인터랙티브 가이드 | connascence.io |
| Page-Jones 원저 | What Every Programmer Should Know About Object-Oriented Design (Dorset House, 1996) |
| OpenAPI 계약 우선 표준 | openapis.org |
| AsyncAPI (비동기 API 계약) | asyncapi.com |
책 다른 장 안내
| 장 | 설명 |
|---|---|
| 5장 | 모듈 결합 6수준 — 6장 공생성의 선수 모델 |
| 7장 | 통합 강도 — 5장 모듈 결합 + 6장 공생성을 통합한 실용 모델 (4수준: 계약·모델·기능·침입 결합) |
| 8장 | 거리 — 높은 공생성 모듈을 가까이 두는 설계 원리 |
| 10장 | 결합 균형 — 공생성 수준별 언제 받아들일 만한가 |
부록 D. 연습문제 풀이
1. 분류
위의 calculate_area(width, height) 호출은 이름 공생성과 유형 공생성이 함께 있다. 함수명 calculate_area와 인수명 width·height를 공유하는 이름 공생성, 두 인수가 숫자 타입이어야 하는 유형 공생성이다. calculate_area(10, 5) 호출에서 width=10·height=5로 순서를 가정하지만, 이 경우는 곱셈의 교환법칙이 성립하므로 위치 공생성의 실질적 위험은 없다. 단, 동일 타입 여러 인수를 쓰는 구조 자체는 위치 공생성 신호이므로 Named 인수 사용이 권장된다.
2. 리팩토링 — 의미 공생성 제거
# 열거형으로 status_code 마법 값 제거
from enum import Enum
class OrderStatus(Enum):
Pending = 1
Shipped = 2
Delivered = 3
Cancelled = 4
def set_order_status(order_id: str, status: OrderStatus):
db.update("orders", {"status": status.value}, {"id": order_id})
# 호출: 의미가 명확
set_order_status("ORD-001", OrderStatus.Shipped)
이제 status_code=2가 무엇인지 별도 주석·코드북 없이 OrderStatus.Shipped로 자기 설명이 된다. 컴파일러(또는 런타임)가 유효하지 않은 값을 거부한다.
3. 분석 — Redis 공유 키
두 서비스 A·B가 Redis 같은 키에 쓰고 상대방의 변경을 즉시 읽는다는 강한 일관성 가정 하에 동일 공생성이다. 정확히 같은 Redis 인스턴스의 상태를 공유하고, 한 서비스의 쓰기가 다른 서비스에 즉시 관찰되어야 하기 때문이다. 만약 두 서비스가 메시지 버스로 통신하고 트랜잭션 일관성을 가정하지 않는다면, 동일 공생성이 아닐 수 있다.
4. 판단 — 타이밍 공생성 완전 제거
아니다 — 비즈니스 요구사항에서 비롯된 타이밍 공생성은 제거할 수 없다. DB 연결 30초 타임아웃은 "연결 후 30초 동안 아무 쿼리도 없으면 연결을 닫는다"는 요구사항 자체가 두 동작(연결 열기·타임아웃 닫기) 사이의 정확한 시간 간격에 의존하므로, 아무리 리팩토링해도 그 30초 의존은 제거할 수 없다. 제거할 수 있는 타이밍 공생성은 구현 부주의로 생긴 것(예: 클록 이중 조회)이며, 비즈니스 요구 자체로 생긴 타이밍 의존은 관리 대상이지 제거 대상이 아니다.
5. 종합 — process_payment 공생성 분석
| 공생성 | 낮추는 방법 |
|---|---|
| 이름 공생성 | 실질적으로 낮출 수 없음 — 이름 없이 참조 불가 |
| 유형 공생성 | 타입 힌트·계약 명시로 명확화는 가능, 완전 제거 불가 |
| 의미 공생성 | transaction_type=3 → TransactionType.Charge 열거형으로 낮춤 |
| 알고리즘 공생성 | 암호화 알고리즘을 공유 라이브러리 상수로 단일화 |
| 위치 공생성 | 반환값 (res, balance, tran_id) → Named 튜플 또는 데이터 클래스로 낮춤 |
이름·유형 공생성은 모듈 간 통신의 기본 비용이므로 완전 제거는 불가. 의미·위치·알고리즘 공생성은 리팩토링으로 낮출 수 있다.
클릭하거나 Space를 눌러 뒤집기